module Main where

import Graphics.UI.WX
import System.Random

------------------------------------------------------------------------

{-
Some internal constants.

The mine field's size is sizeX x sizeY.
The number of mines is mines.
-}

sizeX, sizeY, mines :: Int
sizeX  = 25
sizeY  = 13
mines  = 40

------------------------------------------------------------------------

main :: IO ()
main = start mineFrame

-- mineFrame generates random mine positions and creates a window
-- representing the mine field
mineFrame :: IO ()
mineFrame =
  do frm <- frame [ text := "Mine Sweeper" ]
     mns <- placeMines
     fld <- field frm mns
     cls <- button frm [ text := "Close", on command := close frm ]
     set frm [ layout :=
                 column 5
                 [ grid 1 1
                   [ [ widget but
                     | but <- row
                     ]
                   | row <- buttons fld
                   ]
                 , floatCenter (widget cls)
                 ]
             ]

------------------------------------------------------------------------

-- a position is a pair of integers, representing the coordinates
type Pos = (Int,Int)

-- placeMines creates mines random valid positions
placeMines :: IO [Pos]
placeMines =
  do sequence [ do i <- randomRIO (0,sizeX-1)
                   j <- randomRIO (0,sizeY-1)
                   return (i,j)
              | k <- [1..mines]
              ]

------------------------------------------------------------------------

-- a field is a matrix of buttons and a variable matrix of statusses
data Field =
  Field
  { buttons :: [[Button ()]]
  , status  :: Var [[Status]]
  }

-- field creates a matrix of buttons, associating the instructions "clicked"
-- with each cell
field :: Frame () -> [Pos] -> IO Field
field frm mns =
  do buts <- sequence
               [ sequence
                   [ smallButton frm [ fontWeight := WeightBold ]
                   | st <- row
                   ]
               | row <- sts
               ]
     var <- variable []
     let fld = Field buts var
     sequence_
       [ set but [ on command := do sts <- getStatus fld 
                                    setStatus fld (peek (i,j) mns sts)
                 ]
       | (row,j) <- buts `zip` [0..]
       , (but,i) <- row `zip` [0..]
       ]
     setStatus fld sts
     return fld
 where
  sts = [ [ Hidden | i <- [1..sizeX] ] | j <- [1..sizeY] ]

-- getStatus fld returns the current status matrix
getStatus :: Field -> IO [[Status]]
getStatus fld = get (status fld) value

-- setStatus fld sts updates the current status matrix, also adapting
-- the visual appearence of the buttons
setStatus :: Field -> [[Status]] -> IO ()
setStatus fld sts =
  do set (status fld) [ value := sts ]
     sequence_
       [ stat but st
       | (str,butr) <- sts `zip` buttons fld
       , (st,but) <- str `zip` butr
       ]
 where
  stat but st =
    do set but [ bgcolor := colr st ]
       set but [ text := txt st ]

-- colr st returns the color associated with the status st
colr :: Status -> Color
colr Hidden      = grey
colr (Cleared _) = white
colr Mine        = red
colr Dead        = yellow

-- txt st returns the text on the button for the status st
txt :: Status -> String
txt (Cleared n) | n > 0 = show n
txt _                   = ""

------------------------------------------------------------------------

{-
The code below is completely GUI-independent.
-}

------------------------------------------------------------------------

{-
Status: a datatype for the status of a cell.

A cell can be Hidden (nothing is known yet), Cleared k (there was no mine, and
k mines surround this cell), Mine (there was a mine), and Dead (a mine has
been discovered somewhere else and now the cell is disabled).
-}

data Status
  = Hidden
  | Cleared Int
  | Mine
  | Dead
 deriving ( Show, Eq )

{-
peek p mns sts calculates what happens to all statusses when a user
clicks a the cell with position p; one of three things happens:

* there is a mine in the cell, all mines are revealed and the board
  is disabled

* nothing happens because the cell has already been clicked

* there is no mine, and the number of surrounding mines is calculated.
  if there are no surrounding mines, all surrounding cells are
  automatically clicked as well
-}

-- peek p mns sts calculates the result of clicking on position p in sts
peek :: Pos -> [Pos] -> [[Status]] -> [[Status]]
peek p mns sts
  | p `elem` mns         = mapPos mineOrDead sts
  | (sts ?? p) /= Hidden = sts
  | otherwise            = compose [ peek p' mns | n == 0, p' <- sur ] sts'
 where
  sur  = surrounds p
  n    = length [ p' | p' <- sur, p' `elem` mns ]
  sts' = mapPos clear sts
  
  mineOrDead p' _
    | p' `elem` mns = Mine
    | otherwise     = Dead
  
  clear p' st
    | p == p'   = Cleared n
    | otherwise = st

  xys ?? (i,j) = xys !! j !! i
  compose      = foldr (.) id

-- surrounds p computes all valid positions that surround the position p
surrounds :: (Int,Int) -> [(Int,Int)]
surrounds (i,j) =
  [ (i',j')
  | di <- [-1,0,1]
  , let i' = i+di
  , 0 <= i' && i' < sizeX
  , dj <- [-1,0,1]
  , let j' = j+dj
  , 0 <= j' && j' < sizeY
  , i' /= i || j' /= j
  ]

-- mapPos f fld applies f to all cells in the field fld, also providing
-- the position of the cell
mapPos :: (Pos -> a -> b) -> [[a]] -> [[b]]
mapPos f fld =
  [ [ f (i,j) st
    | (st,i) <- row `zip` [0..]
    ]
  | (row,j) <- fld `zip` [0..]
  ]
  
------------------------------------------------------------------------

